Popular Searches
Popular Course Categories
Popular Courses

Flutter Login & Registration UI

Flutter Login & Registration UI

Flutter Forms & User Input

Flutter Login & Registration UI – Detailed Notes

Login and registration screens are common authentication interfaces in Flutter applications. A login screen generally collects an email/username and password, while a registration screen collects information such as name, email, password, confirm password, phone number, and other required details.

Flutter provides widgets such as Form, TextFormField, TextEditingController, ElevatedButton, Checkbox, InputDecoration, and validation APIs that can be combined to create structured authentication interfaces.


1. What Is a Login UI?

A Login UI is an interface through which an existing user enters authentication information to access an application.

Common Login Fields

  • Email address or username
  • Password
  • Remember me option
  • Login button
  • Forgot password link
  • Registration link
  • Optional social login buttons

Typical Login Flow

  1. User opens the login screen.
  2. User enters email and password.
  3. Input fields are validated.
  4. The application sends the credentials to an authentication service.
  5. If authentication succeeds, the user is redirected to the application dashboard/home screen.
  6. If authentication fails, an appropriate error message is displayed.

2. What Is a Registration UI?

A Registration UI allows a new user to create an account.

Common Registration Fields

  • Full name
  • Email address
  • Phone number
  • Password
  • Confirm password
  • Terms and conditions checkbox
  • Register button
  • Link to the Login screen

Typical Registration Flow

  1. User opens the registration screen.
  2. User enters personal and account information.
  3. Application validates each field.
  4. Application verifies that passwords match.
  5. Application submits the data to the backend.
  6. After successful registration, the user can log in.

3. Important Flutter Widgets for Authentication UI

Widget Purpose
Form Groups multiple form fields and provides form-level operations such as validation.
TextFormField Creates a text input field that integrates with Form and supports validation.
TextEditingController Reads and controls the current text entered into an input field.
InputDecoration Styles fields using labels, hints, icons, borders, and error messages.
ElevatedButton Creates buttons such as Login and Register.
TextButton Useful for actions such as Forgot Password or Create Account.
Checkbox Useful for Remember Me or Terms and Conditions.
Icon Adds visual icons to authentication fields and buttons.
Navigator Moves between Login, Registration, Forgot Password, and Home screens.

TextFormField integrates a text field with a FormField, making it convenient for form validation and error display. :contentReference[oaicite:0]{index=0}


4. Basic Login UI Structure

Scaffold
└── SafeArea
    └── SingleChildScrollView
        └── Padding
            └── Column
                ├── Logo
                ├── Welcome Text
                ├── Form
                │   ├── Email Field
                │   ├── Password Field
                │   └── Login Button
                ├── Forgot Password
                └── Create Account

5. Creating a Login Screen

The following example creates a basic login interface using Flutter Material widgets.

import 'package:flutter/material.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State createState() => _LoginScreenState();
}

class _LoginScreenState extends State {
  final _formKey = GlobalKey();
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Login'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              const Icon(
                Icons.lock,
                size: 80,
              ),
              const SizedBox(height: 20),
              const Text(
                'Welcome Back',
                style: TextStyle(
                  fontSize: 28,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 30),
              TextFormField(
                controller: emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  hintText: 'Enter your email',
                  prefixIcon: Icon(Icons.email),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Please enter your email';
                  }
                  if (!value.contains('@')) {
                    return 'Please enter a valid email';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: passwordController,
                obscureText: true,
                decoration: const InputDecoration(
                  labelText: 'Password',
                  hintText: 'Enter your password',
                  prefixIcon: Icon(Icons.lock),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your password';
                  }
                  if (value.length < 6) {
                    return 'Password must be at least 6 characters';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      print('Login successful');
                    }
                  },
                  child: const Text('Login'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Flutter's recommended form-validation pattern uses a GlobalKey, TextFormField validators, and FormState.validate(). :contentReference[oaicite:1]{index=1}


6. Creating an Email Input Field

TextFormField(
  controller: emailController,
  keyboardType: TextInputType.emailAddress,
  decoration: const InputDecoration(
    labelText: 'Email Address',
    hintText: '[email protected]',
    prefixIcon: Icon(Icons.email),
    border: OutlineInputBorder(),
  ),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'Email is required';
    }

    if (!value.contains('@')) {
      return 'Enter a valid email address';
    }

    return null;
  },
)

Important Properties

  • keyboardType controls the appropriate keyboard type.
  • prefixIcon displays an icon before the input.
  • hintText provides an example or instruction.
  • labelText identifies the field.
  • validator checks whether the entered value is valid.

7. Creating a Password Field

Password fields should hide the entered characters. Flutter provides the obscureText property for this purpose.

TextFormField(
  controller: passwordController,
  obscureText: true,
  decoration: const InputDecoration(
    labelText: 'Password',
    prefixIcon: Icon(Icons.lock),
    border: OutlineInputBorder(),
  ),
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Password is required';
    }

    if (value.length < 8) {
      return 'Password must contain at least 8 characters';
    }

    return null;
  },
)

8. Show and Hide Password

A common login UI feature is a visibility icon that allows users to temporarily display or hide their password.

bool _obscurePassword = true;

TextFormField(
  obscureText: _obscurePassword,
  decoration: InputDecoration(
    labelText: 'Password',
    prefixIcon: const Icon(Icons.lock),
    suffixIcon: IconButton(
      icon: Icon(
        _obscurePassword
            ? Icons.visibility
            : Icons.visibility_off,
      ),
      onPressed: () {
        setState(() {
          _obscurePassword = !_obscurePassword;
        });
      },
    ),
    border: const OutlineInputBorder(),
  ),
)

9. Login Form Validation

Validation prevents incomplete or incorrectly formatted information from being submitted.

Common Login Validation Rules

  • Email cannot be empty.
  • Email should have a valid format.
  • Password cannot be empty.
  • Password should satisfy the application's minimum length requirements.
  • Additional security rules can be applied by the backend.
if (_formKey.currentState!.validate()) {
  print('All fields are valid');
} else {
  print('Please correct the errors');
}

When validate() is called, Flutter runs the validators associated with the form fields and reports whether the form is valid. :contentReference[oaicite:2]{index=2}


10. Forgot Password UI

A Forgot Password action is commonly placed below the password field.

Align(
  alignment: Alignment.centerRight,
  child: TextButton(
    onPressed: () {
      print('Forgot password clicked');
    },
    child: const Text('Forgot Password?'),
  ),
)

In a production application, this button can navigate to a password-reset screen.


11. Remember Me Checkbox

A Remember Me checkbox can be used to let users indicate that they want the application to retain an appropriate login/session preference.

bool rememberMe = false;

CheckboxListTile(
  value: rememberMe,
  title: const Text('Remember Me'),
  controlAffinity: ListTileControlAffinity.leading,
  onChanged: (value) {
    setState(() {
      rememberMe = value ?? false;
    });
  },
)

Actual persistence of a login session should be handled securely rather than storing passwords directly on the device.


12. Creating a Registration Screen

A registration screen usually contains more fields than a login screen.

class RegistrationScreen extends StatefulWidget {
  const RegistrationScreen({super.key});

  @override
  State createState() => _RegistrationScreenState();
}

class _RegistrationScreenState extends State {
  final _formKey = GlobalKey();

  final nameController = TextEditingController();
  final emailController = TextEditingController();
  final passwordController = TextEditingController();
  final confirmPasswordController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Create Account'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              const Text(
                'Create Your Account',
                style: TextStyle(
                  fontSize: 26,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 24),
              TextFormField(
                controller: nameController,
                decoration: const InputDecoration(
                  labelText: 'Full Name',
                  prefixIcon: Icon(Icons.person),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Please enter your name';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: emailController,
                keyboardType: TextInputType.emailAddress,
                decoration: const InputDecoration(
                  labelText: 'Email',
                  prefixIcon: Icon(Icons.email),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.trim().isEmpty) {
                    return 'Please enter your email';
                  }
                  if (!value.contains('@')) {
                    return 'Please enter a valid email';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: passwordController,
                obscureText: true,
                decoration: const InputDecoration(
                  labelText: 'Password',
                  prefixIcon: Icon(Icons.lock),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter a password';
                  }
                  if (value.length < 8) {
                    return 'Password must contain at least 8 characters';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 16),
              TextFormField(
                controller: confirmPasswordController,
                obscureText: true,
                decoration: const InputDecoration(
                  labelText: 'Confirm Password',
                  prefixIcon: Icon(Icons.lock_outline),
                  border: OutlineInputBorder(),
                ),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please confirm your password';
                  }
                  if (value != passwordController.text) {
                    return 'Passwords do not match';
                  }
                  return null;
                },
              ),
              const SizedBox(height: 24),
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: () {
                    if (_formKey.currentState!.validate()) {
                      print('Registration successful');
                    }
                  },
                  child: const Text('Register'),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

13. Confirm Password Validation

Registration forms commonly require users to enter the password twice to reduce accidental password-entry mistakes.

validator: (value) {
  if (value == null || value.isEmpty) {
    return 'Please confirm your password';
  }

  if (value != passwordController.text) {
    return 'Passwords do not match';
  }

  return null;
}

14. Adding a Phone Number Field

final phoneController = TextEditingController();

TextFormField(
  controller: phoneController,
  keyboardType: TextInputType.phone,
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    prefixIcon: Icon(Icons.phone),
    border: OutlineInputBorder(),
  ),
  validator: (value) {
    if (value == null || value.trim().isEmpty) {
      return 'Please enter your phone number';
    }

    if (value.length < 10) {
      return 'Enter a valid phone number';
    }

    return null;
  },
)

15. Terms and Conditions Checkbox

Registration forms may require the user to accept terms and conditions before creating an account.

bool acceptedTerms = false;

CheckboxListTile(
  value: acceptedTerms,
  title: const Text(
    'I agree to the Terms and Conditions',
  ),
  onChanged: (value) {
    setState(() {
      acceptedTerms = value ?? false;
    });
  },
)

Checking Before Registration

if (!_formKey.currentState!.validate()) {
  return;
}

if (!acceptedTerms) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Please accept the Terms and Conditions'),
    ),
  );
  return;
}

print('Registration can continue');

16. Login and Registration Navigation

A login screen should provide a way to move to the registration screen, while a registration screen should provide a way to return to login.

Navigate to Registration

TextButton(
  onPressed: () {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const RegistrationScreen(),
      ),
    );
  },
  child: const Text('Create an Account'),
)

Navigate Back to Login

TextButton(
  onPressed: () {
    Navigator.pop(context);
  },
  child: const Text('Already have an account? Login'),
)

17. Complete Login UI Example

import 'package:flutter/material.dart';

class LoginScreen extends StatefulWidget {
  const LoginScreen({super.key});

  @override
  State createState() => _LoginScreenState();
}

class _LoginScreenState extends State {
  final _formKey = GlobalKey();
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  bool obscurePassword = true;
  bool rememberMe = false;
  bool isLoading = false;

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  Future login() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    setState(() {
      isLoading = true;
    });

    await Future.delayed(const Duration(seconds: 2));

    if (!mounted) {
      return;
    }

    setState(() {
      isLoading = false;
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Login request completed'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const SizedBox(height: 40),
                const Icon(
                  Icons.account_circle,
                  size: 90,
                ),
                const SizedBox(height: 20),
                const Text(
                  'Welcome Back!',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 30,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                const Text(
                  'Login to continue',
                  textAlign: TextAlign.center,
                ),
                const SizedBox(height: 35),
                TextFormField(
                  controller: emailController,
                  keyboardType: TextInputType.emailAddress,
                  decoration: const InputDecoration(
                    labelText: 'Email',
                    prefixIcon: Icon(Icons.email),
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.trim().isEmpty) {
                      return 'Email is required';
                    }

                    if (!value.contains('@')) {
                      return 'Enter a valid email';
                    }

                    return null;
                  },
                ),
                const SizedBox(height: 18),
                TextFormField(
                  controller: passwordController,
                  obscureText: obscurePassword,
                  decoration: InputDecoration(
                    labelText: 'Password',
                    prefixIcon: const Icon(Icons.lock),
                    suffixIcon: IconButton(
                      icon: Icon(
                        obscurePassword
                            ? Icons.visibility
                            : Icons.visibility_off,
                      ),
                      onPressed: () {
                        setState(() {
                          obscurePassword = !obscurePassword;
                        });
                      },
                    ),
                    border: const OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Password is required';
                    }

                    if (value.length < 6) {
                      return 'Password must contain at least 6 characters';
                    }

                    return null;
                  },
                ),
                Align(
                  alignment: Alignment.centerRight,
                  child: TextButton(
                    onPressed: () {},
                    child: const Text('Forgot Password?'),
                  ),
                ),
                CheckboxListTile(
                  value: rememberMe,
                  title: const Text('Remember Me'),
                  contentPadding: EdgeInsets.zero,
                  onChanged: (value) {
                    setState(() {
                      rememberMe = value ?? false;
                    });
                  },
                ),
                const SizedBox(height: 15),
                SizedBox(
                  height: 50,
                  child: ElevatedButton(
                    onPressed: isLoading ? null : login,
                    child: isLoading
                        ? const CircularProgressIndicator()
                        : const Text('Login'),
                  ),
                ),
                const SizedBox(height: 20),
                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    const Text("Don't have an account?"),
                    TextButton(
                      onPressed: () {},
                      child: const Text('Register'),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

18. Complete Registration UI Example

import 'package:flutter/material.dart';

class RegistrationScreen extends StatefulWidget {
  const RegistrationScreen({super.key});

  @override
  State createState() => _RegistrationScreenState();
}

class _RegistrationScreenState extends State {
  final _formKey = GlobalKey();

  final nameController = TextEditingController();
  final emailController = TextEditingController();
  final phoneController = TextEditingController();
  final passwordController = TextEditingController();
  final confirmPasswordController = TextEditingController();

  bool acceptedTerms = false;
  bool obscurePassword = true;
  bool obscureConfirmPassword = true;
  bool isLoading = false;

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    phoneController.dispose();
    passwordController.dispose();
    confirmPasswordController.dispose();
    super.dispose();
  }

  Future register() async {
    if (!_formKey.currentState!.validate()) {
      return;
    }

    if (!acceptedTerms) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please accept the Terms and Conditions'),
        ),
      );
      return;
    }

    setState(() {
      isLoading = true;
    });

    await Future.delayed(const Duration(seconds: 2));

    if (!mounted) {
      return;
    }

    setState(() {
      isLoading = false;
    });

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Registration completed'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Register'),
      ),
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const Text(
                  'Create Account',
                  textAlign: TextAlign.center,
                  style: TextStyle(
                    fontSize: 30,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 30),
                TextFormField(
                  controller: nameController,
                  decoration: const InputDecoration(
                    labelText: 'Full Name',
                    prefixIcon: Icon(Icons.person),
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.trim().isEmpty) {
                      return 'Please enter your name';
                    }
                    return null;
                  },
                ),
                const SizedBox(height: 16),
                TextFormField(
                  controller: emailController,
                  keyboardType: TextInputType.emailAddress,
                  decoration: const InputDecoration(
                    labelText: 'Email',
                    prefixIcon: Icon(Icons.email),
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.trim().isEmpty) {
                      return 'Please enter your email';
                    }

                    if (!value.contains('@')) {
                      return 'Please enter a valid email';
                    }

                    return null;
                  },
                ),
                const SizedBox(height: 16),
                TextFormField(
                  controller: phoneController,
                  keyboardType: TextInputType.phone,
                  decoration: const InputDecoration(
                    labelText: 'Phone Number',
                    prefixIcon: Icon(Icons.phone),
                    border: OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.trim().isEmpty) {
                      return 'Please enter your phone number';
                    }

                    if (value.length < 10) {
                      return 'Please enter a valid phone number';
                    }

                    return null;
                  },
                ),
                const SizedBox(height: 16),
                TextFormField(
                  controller: passwordController,
                  obscureText: obscurePassword,
                  decoration: InputDecoration(
                    labelText: 'Password',
                    prefixIcon: const Icon(Icons.lock),
                    suffixIcon: IconButton(
                      icon: Icon(
                        obscurePassword
                            ? Icons.visibility
                            : Icons.visibility_off,
                      ),
                      onPressed: () {
                        setState(() {
                          obscurePassword = !obscurePassword;
                        });
                      },
                    ),
                    border: const OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter a password';
                    }

                    if (value.length < 8) {
                      return 'Password must contain at least 8 characters';
                    }

                    return null;
                  },
                ),
                const SizedBox(height: 16),
                TextFormField(
                  controller: confirmPasswordController,
                  obscureText: obscureConfirmPassword,
                  decoration: InputDecoration(
                    labelText: 'Confirm Password',
                    prefixIcon: const Icon(Icons.lock_outline),
                    suffixIcon: IconButton(
                      icon: Icon(
                        obscureConfirmPassword
                            ? Icons.visibility
                            : Icons.visibility_off,
                      ),
                      onPressed: () {
                        setState(() {
                          obscureConfirmPassword =
                              !obscureConfirmPassword;
                        });
                      },
                    ),
                    border: const OutlineInputBorder(),
                  ),
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please confirm your password';
                    }

                    if (value != passwordController.text) {
                      return 'Passwords do not match';
                    }

                    return null;
                  },
                ),
                const SizedBox(height: 10),
                CheckboxListTile(
                  value: acceptedTerms,
                  contentPadding: EdgeInsets.zero,
                  title: const Text(
                    'I agree to the Terms and Conditions',
                  ),
                  onChanged: (value) {
                    setState(() {
                      acceptedTerms = value ?? false;
                    });
                  },
                ),
                const SizedBox(height: 20),
                SizedBox(
                  height: 50,
                  child: ElevatedButton(
                    onPressed: isLoading ? null : register,
                    child: isLoading
                        ? const CircularProgressIndicator()
                        : const Text('Register'),
                  ),
                ),
                const SizedBox(height: 15),
                TextButton(
                  onPressed: () {
                    Navigator.pop(context);
                  },
                  child: const Text('Already have an account? Login'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

19. Using TextEditingController

TextEditingController allows an application to retrieve and control the current text in an input field. Controllers should be disposed when they are no longer needed. :contentReference[oaicite:3]{index=3}

final emailController = TextEditingController();

TextFormField(
  controller: emailController,
)

void login() {
  final email = emailController.text.trim();
  print(email);
}

@override
void dispose() {
  emailController.dispose();
  super.dispose();
}

20. Auto Validation

Flutter provides AutovalidateMode for controlling when form fields automatically run their validators.

TextFormField(
  autovalidateMode: AutovalidateMode.onUserInteraction,
  validator: (value) {
    if (value == null || value.isEmpty) {
      return 'Email is required';
    }
    return null;
  },
)

Common Modes

Mode Description
disabled Automatic validation is disabled.
always The field is automatically validated.
onUserInteraction Validation occurs after the user interacts with the field.
onUnfocus Validation can occur when the field loses focus.
onUserInteractionIfError Provides automatic validation behavior focused on user interaction when an error exists.

The current Flutter API documents these auto-validation modes and their behavior. :contentReference[oaicite:4]{index=4}


21. Responsive Login and Registration UI

Authentication screens should work on phones, tablets, and larger displays.

Useful Responsive Techniques

  • Use SafeArea to avoid system UI areas.
  • Use SingleChildScrollView to prevent keyboard overflow.
  • Use ConstrainedBox or Center for large screens.
  • Avoid unnecessarily fixed widths.
  • Use responsive padding.
  • Use appropriate keyboard types.
Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 500,
    ),
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: loginForm,
    ),
  ),
)

22. Login and Registration Card UI

A Card-based design can visually separate the authentication form from the application background.

Center(
  child: Card(
    margin: const EdgeInsets.all(20),
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: Form(
        key: _formKey,
        child: Column(
          children: [
            const Text(
              'Login',
              style: TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 20),
            // Email field
            // Password field
            // Login button
          ],
        ),
      ),
    ),
  ),
)

23. InputDecoration for Professional UI

InputDecoration(
  labelText: 'Email',
  hintText: 'Enter your email',
  prefixIcon: const Icon(Icons.email),
  border: OutlineInputBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  enabledBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(12),
  ),
  focusedBorder: OutlineInputBorder(
    borderRadius: BorderRadius.circular(12),
  ),
)

Useful InputDecoration Properties

  • labelText
  • hintText
  • prefixIcon
  • suffixIcon
  • helperText
  • errorText
  • border
  • enabledBorder
  • focusedBorder

24. Focus Management

Focus management can improve the experience when users move between multiple authentication fields.

final emailFocus = FocusNode();
final passwordFocus = FocusNode();

TextFormField(
  focusNode: emailFocus,
  textInputAction: TextInputAction.next,
  onFieldSubmitted: (_) {
    passwordFocus.requestFocus();
  },
)

TextFormField(
  focusNode: passwordFocus,
  obscureText: true,
  textInputAction: TextInputAction.done,
)

Long-lived FocusNode objects should be managed by a State object and disposed when no longer needed. :contentReference[oaicite:5]{index=5}


25. Loading State During Login

When login or registration communicates with a backend, the button can display a loading indicator while the request is running.

bool isLoading = false;

ElevatedButton(
  onPressed: isLoading ? null : login,
  child: isLoading
      ? const CircularProgressIndicator()
      : const Text('Login'),
)

Disabling the button while a request is running can help prevent accidental repeated submissions.


26. Displaying Authentication Errors

Client-side validation and server-side authentication are different responsibilities. Even if the form is valid, the backend may reject the credentials or registration request.

try {
  // Call authentication API here.
} catch (e) {
  if (!mounted) {
    return;
  }

  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(
      content: Text('Unable to complete the request'),
    ),
  );
}

27. Example Login Authentication Flow

Future login() async {
  if (!_formKey.currentState!.validate()) {
    return;
  }

  final email = emailController.text.trim();
  final password = passwordController.text;

  setState(() {
    isLoading = true;
  });

  try {
    // Example:
    // await authService.login(
    //   email: email,
    //   password: password,
    // );

    await Future.delayed(const Duration(seconds: 2));

    if (!mounted) {
      return;
    }

    Navigator.pushReplacement(
      context,
      MaterialPageRoute(
        builder: (context) => const HomeScreen(),
      ),
    );
  } catch (e) {
    if (!mounted) {
      return;
    }

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Login failed'),
      ),
    );
  } finally {
    if (mounted) {
      setState(() {
        isLoading = false;
      });
    }
  }
}

28. Example Registration Authentication Flow

Future register() async {
  if (!_formKey.currentState!.validate()) {
    return;
  }

  if (!acceptedTerms) {
    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Please accept the Terms and Conditions'),
      ),
    );
    return;
  }

  final name = nameController.text.trim();
  final email = emailController.text.trim();
  final password = passwordController.text;

  setState(() {
    isLoading = true;
  });

  try {
    // Send registration data to your backend.
    // await authService.register(
    //   name: name,
    //   email: email,
    //   password: password,
    // );

    await Future.delayed(const Duration(seconds: 2));

    if (!mounted) {
      return;
    }

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Account created successfully'),
      ),
    );
  } catch (e) {
    if (!mounted) {
      return;
    }

    ScaffoldMessenger.of(context).showSnackBar(
      const SnackBar(
        content: Text('Registration failed'),
      ),
    );
  } finally {
    if (mounted) {
      setState(() {
        isLoading = false;
      });
    }
  }
}

29. Difference Between Login and Registration UI

Feature Login Registration
Purpose Authenticate an existing user Create a new user account
Email Usually required Usually required
Password Required Required
Confirm Password Usually not required Commonly required
Name Usually not required Commonly required
Phone Usually optional May be required
Terms Checkbox Usually not required Commonly included
Forgot Password Common Not normally included

30. Common Mistakes

  • Not wrapping related fields inside a Form.
  • Not assigning a GlobalKey when using key-based validation.
  • Forgetting to validate before submitting data.
  • Using weak or incomplete client-side validation.
  • Not disposing TextEditingController instances.
  • Not disposing FocusNode instances when they are created and owned by the screen.
  • Storing passwords directly in insecure local storage.
  • Allowing repeated login or registration requests while a request is already running.
  • Making authentication forms too wide on desktop or tablet layouts.
  • Not using SingleChildScrollView when the form can exceed the available screen height.
  • Showing unclear error messages.

31. Best Practices

  • Use Form and TextFormField for structured forms.
  • Keep validation rules readable and reusable.
  • Use clear labels and helpful hints.
  • Use obscureText for password fields.
  • Provide a password visibility control when appropriate.
  • Use suitable keyboard types such as TextInputType.emailAddress and TextInputType.phone.
  • Disable the submit button while an authentication request is running.
  • Show clear feedback when authentication succeeds or fails.
  • Keep authentication logic separate from the UI where practical.
  • Never treat client-side validation as a replacement for server-side validation.
  • Dispose controllers and focus nodes properly.
  • Make the authentication UI responsive.
  • Use secure authentication and credential-storage practices for production applications.

32. Suggested Authentication Project Structure

lib/
├── main.dart
├── screens/
│   ├── login_screen.dart
│   ├── registration_screen.dart
│   ├── forgot_password_screen.dart
│   └── home_screen.dart
├── widgets/
│   ├── custom_text_field.dart
│   └── auth_button.dart
├── services/
│   └── auth_service.dart
├── models/
│   └── user_model.dart
└── validators/
    └── form_validators.dart

33. Reusable Email Validator

String? validateEmail(String? value) {
  if (value == null || value.trim().isEmpty) {
    return 'Email is required';
  }

  final emailRegex = RegExp(
    r'^[^@\s]+@[^@\s]+\.[^@\s]+$',
  );

  if (!emailRegex.hasMatch(value.trim())) {
    return 'Enter a valid email address';
  }

  return null;
}

34. Reusable Password Validator

String? validatePassword(String? value) {
  if (value == null || value.isEmpty) {
    return 'Password is required';
  }

  if (value.length < 8) {
    return 'Password must contain at least 8 characters';
  }

  return null;
}

35. Using Reusable Validators

TextFormField(
  controller: emailController,
  validator: validateEmail,
  decoration: const InputDecoration(
    labelText: 'Email',
    border: OutlineInputBorder(),
  ),
)

TextFormField(
  controller: passwordController,
  obscureText: true,
  validator: validatePassword,
  decoration: const InputDecoration(
    labelText: 'Password',
    border: OutlineInputBorder(),
  ),
)

36. Authentication UI Flow

App Start
   ↓
Login Screen
   ↓
User enters email/password
   ↓
Client-side validation
   ↓
Valid?
 ┌─No──────────────→ Show field errors
 │
 Yes
 ↓
Send authentication request
 ↓
Server authentication
 ↓
Success? 
 ┌─No──────────────→ Show authentication error
 │
 Yes
 ↓
Home/Dashboard Screen

37. Login and Registration UI Checklist

  • Login screen created
  • Registration screen created
  • Email field added
  • Password field added
  • Password visibility toggle added
  • Confirm password field added
  • Form validation implemented
  • Forgot password action added
  • Remember Me option added if required
  • Terms and Conditions option added for registration
  • Navigation between Login and Registration implemented
  • Loading state implemented
  • Authentication error handling implemented
  • Controllers disposed properly
  • Focus nodes disposed when owned by the screen
  • Responsive layout tested

38. Mini Project: Flutter Authentication UI

Create an authentication application containing the following screens:

  1. Login Screen
  2. Registration Screen
  3. Forgot Password Screen
  4. Home Screen

Login Screen Requirements

  • Email input
  • Password input
  • Password visibility toggle
  • Remember Me checkbox
  • Login button
  • Forgot Password button
  • Registration navigation

Registration Screen Requirements

  • Full name
  • Email
  • Phone number
  • Password
  • Confirm password
  • Terms and Conditions checkbox
  • Register button
  • Login navigation

Learning Objectives

  • Understand Flutter authentication UI structure.
  • Build reusable form fields.
  • Implement form validation.
  • Use controllers to retrieve user input.
  • Manage password visibility.
  • Navigate between authentication screens.
  • Handle loading and error states.
  • Prepare an authentication UI for backend integration.

39. Official Flutter Resources


40. Flutter Training Resources

Learn more about Flutter development and training through the following resources:


41. Quick Revision

Concept Purpose
Form Groups and manages form fields.
GlobalKey Provides access to form state and validation methods.
TextFormField Creates a form-aware text input field.
validator Checks whether input is valid.
TextEditingController Reads and controls field text.
obscureText Hides password characters.
InputDecoration Controls field appearance.
AutovalidateMode Controls automatic field validation.
Navigator Handles navigation between authentication screens.
ElevatedButton Creates primary actions such as Login/Register.

42. Key Takeaways

  • Flutter provides powerful widgets for creating Login and Registration interfaces.
  • Form and TextFormField provide a structured approach to form validation.
  • GlobalKey can be used to access the form and call validate().
  • TextEditingController can be used to retrieve user-entered values.
  • Password fields can use obscureText and a visibility toggle.
  • Registration forms can validate password confirmation and terms acceptance.
  • Authentication requests should provide loading and error states.
  • Client-side validation improves user experience but does not replace backend validation.
  • Controllers and focus nodes should be properly disposed.
  • A good authentication UI should be clear, responsive, accessible, and easy to navigate.
Previous Next
whatsapp